﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ionic.Zip;
using System.IO;
using System.Xml;
using System.Xml.Linq;

namespace MyPublisher.Package
{
    public class MyPackage : IDisposable
    {
        public static MyPackage CreatePackage()
        {
            ZipFile zip = null;
            try
            {
                zip = new ZipFile();
                zip.AddDirectoryByName("CONTENT");
                zip.AddDirectoryByName("META-INF");
                zip.AddDirectoryByName("PUBLICATION");
                zip.AddEntry("mimetype", "application/mpp+zip", Encoding.UTF8);
            }
            catch (Exception e)
            {
                throw new PackageException("Failed to create zip file ", e);
            }

            return new MyPackage(zip, CreateContainerDocument());
        }
        public static MyPackage OpenPackage(string fileName)
        {
            ZipFile zip = null;
            try
            {
                zip = ZipFile.Read(fileName);
            }
            catch (Exception e)
            {
                throw new PackageException("Failed to read file " + fileName, e);
            }
            if (zip == null)
            {
                return null;
            }

            XDocument document = null;
            string tmpPath = string.Empty;
            try
            {
                string onputDir = Path.GetTempPath();
                tmpPath = Path.Combine(onputDir, "META-INF");

                ZipEntry entry = zip["META-INF/container.xml"];
                entry.Extract(tmpPath, ExtractExistingFileAction.OverwriteSilently);
                string path = Path.Combine(tmpPath, "META-INF\\container.xml");
                document = XDocument.Load(path);
            }
            catch (Exception e)
            {
                if (zip != null)
                {
                    zip.Dispose();
                }
                throw new PackageException("Failed to read container.xml ", e);
            }
            finally
            {
                try { Directory.Delete(tmpPath,true); }
                catch { }
            }

            return new MyPackage(zip, document);
        }

        private ZipFile Zip { get; set; }
        private XDocument ContainerDocument { get; set; }
        protected MyPackage(ZipFile zip,XDocument containerDocument) 
        {
            this.Zip = zip;
            this.ContainerDocument = containerDocument;
        }

        public void AddPart(PackagePart part)
        {
            string zipDir = part.Type.ToString().ToUpper();
            string entryName = string.Empty;

            try
            {
                //add part
                if (IsFile(part.Path))
                {
                    FileInfo fi = new FileInfo(part.Path);
                    entryName = zipDir + @"/" + fi.Name;
                    if (Zip[entryName] != null)
                    {
                        Zip.UpdateFile(part.Path, zipDir);
                    }
                    else
                    {
                        Zip.AddFile(part.Path, zipDir);
                    }
                }
                else
                {
                    DirectoryInfo di = new DirectoryInfo(part.Path);
                    entryName = zipDir + @"/" + di.Name;
                    if (Zip[entryName] != null)
                    {
                        Zip.AddDirectory(part.Path, entryName);
                    }
                    else
                    {
                        Zip.UpdateDirectory(part.Path, entryName);
                    }
                }
            }
            catch (Exception e)
            {
                throw new PackageException("Failed to add part " + part.Path, e);
            }

            //write to containter
            try
            {
                XElement containerelement = ContainerDocument.Root;

                ContentPart contentPart = part as ContentPart;
                if (contentPart != null)
                {
                    XElement contentsElement = containerelement.Element("contents");
                    XElement element = (from c in contentsElement.Elements() 
                                        where c.Attribute("path").Value==entryName
                                            select c).SingleOrDefault();
                    if (element == null)
                    {
                        element = new XElement("Content",
                            new XAttribute("path", entryName),
                            new XAttribute("media-type", part.MediaType),
                            new XAttribute("link", contentPart.Link),
                            new XAttribute("title", part.Title));
                        contentsElement.Add(element);
                    }
                    else
                    {
                        element.Attribute("path").Value = entryName;
                        element.Attribute("media-type").Value = part.MediaType;
                        element.Attribute("link").Value = contentPart.Link;
                        element.Attribute("title").Value = part.Title;
                    }
                }

                PublicationPart publicationPart = part as PublicationPart;
                if (publicationPart != null)
                {
                    XElement contentsElement = containerelement.Element("publications");
                    XElement element = (from c in contentsElement.Elements()
                                        where c.Attribute("path").Value == entryName
                                        select c).SingleOrDefault();
                    if (element == null)
                    {
                        element = new XElement("publication",
                            new XAttribute("path", entryName),
                            new XAttribute("media-type", part.MediaType),
                            new XAttribute("title", part.Title));
                        contentsElement.Add(element);
                    }
                    else
                    {
                        element.Attribute("path").Value = entryName;
                        element.Attribute("media-type").Value = part.MediaType;
                        element.Attribute("title").Value = part.Title;
                    }
                }
            }
            catch (Exception e)
            {
                throw new PackageException("Failed to add data to container.xml ",e);
            }
        }

        public void Save(string path)
        {
            string tmpFile = string.Empty;
            try
            {
                tmpFile = Path.GetTempFileName();
                string containerFile = Path.GetDirectoryName(tmpFile);
                containerFile = Path.Combine(containerFile, "container.xml");
                ContainerDocument.Save(containerFile);
                if (Zip["META-INF/container.xml"] != null)
                {
                    Zip.UpdateFile(containerFile, "META-INF");
                }
                else
                {
                    Zip.AddFile(containerFile, "META-INF");
                }

                Zip.Save(path);
            }
            catch (Exception e)
            {
                throw new PackageException("Failed to save to " + path, e);
            }
            finally
            {
                try { File.Delete(tmpFile); }
                catch { }
            }
        }

        private static XDocument CreateContainerDocument()
        {
            XDocument document = new XDocument(new XDeclaration("1.0", "utf-8", null),
                new XElement("container", new XAttribute("version", "1.0"),
                    new XElement("contents"),
                    new XElement("publications")));

            return document;
        }
        private static bool IsFile(string path)
        {
            DirectoryInfo di = new DirectoryInfo(path);
            return !string.IsNullOrEmpty(di.Extension);
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (Zip != null)
            {
                Zip.Dispose();
            }
        }

        #endregion
    }
}
